Initial commit
[RGBW Controller] / RGBW_Controller / RGBW_Controller.ino
1 /*
2 /*
3  * RGB(W) LED IM Dendrite module (for ESP8266)
4  * Created for Interplaymediumâ„¢ project (https://interplaymedium.org)
5  * Copyright Â© 2016 Dmitry Shalnov [interplaymedium.org]
6  * Licensed under the Apache License, Version 2.0
7 */
8
9 #include "../../info"
10
11 #include <ESP8266WiFi.h>
12 #include <WiFiClient.h>
13 #include <ESP8266WebServer.h>
14 #include <ESP8266mDNS.h>
15 #include <EEPROM.h>
16
17 #define VERSION         "0.1.4"
18
19 #define EEPROM_STR_MAX_LEN      16
20 #define EEPROM_OTHER_MAX_LEN    8
21
22 #define EEPROM_STR              0
23 #define EEPROM_STR_FLAG         17      // watch overlap: EEPROM_STR_MAX_LEN
24 #define EEPROM_R                18
25 #define EEPROM_G                19
26 #define EEPROM_B                20
27 #define EEPROM_W                21
28 #define EEPROM_ROT_H            22
29 #define EEPROM_ROT_L            23
30
31 // -------------------- PWM settings  ----------------------------------------------------
32
33 extern "C"{
34         #include "pwm.h"        // Includes of Expressif SDK
35 }
36
37 #define LED1            0
38 #define LED2            2
39 #define LED3            3
40 #define LED4            1
41
42 #define PWM_CHANNELS    4
43
44 #define PWMSTEPS        255
45 #define PWM_PERIOD      5000 // Period of PWM frequency, SDK default: 5000 -> * 200ns ^= 1 kHz
46
47 unsigned int CIEL8[ PWMSTEPS ];
48
49 int R=0, G=0, B=0, W=0;
50 unsigned int Rnew=0, Gnew=0, Bnew=0, Wnew=0;
51 unsigned int rotateDelay = 0;
52 int signR = 1, signG = 1, signB = 1, signW = 1;
53 unsigned int eTimer = 0;
54
55 uint32 pwm_duty_init[PWM_CHANNELS]; // PWM initial duty: OFF by default
56
57 uint32 io_info[PWM_CHANNELS][3] = {
58
59 //      MUX,             FUNC,          PIN
60
61 //      {PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5,     5}, // D1
62 //      {PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4,     4}, // D2
63         {PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0,     LED1}, // D3   0
64         {PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2,     LED2}, // D4   2
65
66         {PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3,     LED3}, //      RX      3
67         {PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1,     LED4}, //      TX      1
68
69 //      {PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14, 14}, // D5
70 //      {PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12, 12}, // D6
71 //      {PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13, 13}, // D7
72 //      {PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15 ,15}, // D8
73
74 //      D0 - not have PWM :-(                16
75 };
76
77 // ------------------- server settings --------------------------------------------------
78
79 #define HOST_DEFAULT                    "im_rgb5"
80
81 char host[ EEPROM_STR_MAX_LEN ];
82
83 const char* ssid        = IM_WIFI_SSID;
84 const char* password    = IM_WIFI_PASS;
85
86 ESP8266WebServer        server(80);
87
88 String          HTTPresp;
89
90 // ----------------- change current R G B (W) to another RGBW set ----------------------
91
92 void changeRGBto( int newR, int newG, int newB, int newW ){
93
94         float stepR, stepG, stepB, stepW;
95         int R2, G2, B2, W2;
96 /*
97 //      int R, G, B, W;
98
99 //      R = EEPROM.read( 0 );
100 //      G = EEPROM.read( 1 );
101 //      B = EEPROM.read( 2 );
102 //      W = EEPROM.read( 3 );
103 */
104         stepR = (newR - R) / (float)PWMSTEPS;
105         stepG = (newG - G) / (float)PWMSTEPS;
106         stepB = (newB - B) / (float)PWMSTEPS;
107         stepW = (newW - W) / (float)PWMSTEPS;
108
109         for ( unsigned int a = 0; a < PWMSTEPS; a++ ){
110
111                 R2 = R + round( stepR * (float)a );
112                 G2 = G + round( stepG * (float)a );
113                 B2 = B + round( stepB * (float)a );
114                 W2 = W + round( stepW * (float)a );
115
116                 pwm_set_duty( CIEL8[R2], 0 );
117                 pwm_set_duty( CIEL8[G2], 1 );
118                 pwm_set_duty( CIEL8[B2], 2 );
119                 pwm_set_duty( CIEL8[W2], 3 );
120
121                 pwm_start(); // commit
122
123                 delay( 500 / PWMSTEPS );
124         }
125 }
126
127 // ----------- explode for selected substring -----------
128
129 String expld( String str, unsigned int numb, char delimiter ){
130         
131         unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0;
132
133         lng = str.length();
134
135         for ( a = 0; a < lng; a++ ){
136                 if ( str.charAt( a ) == delimiter ) { 
137                         p2 = p1;
138                         p1 = a;
139                         if ( cnt == numb ) break;
140                         cnt ++;
141                 }
142         }
143
144         if ( a == lng ) { 
145                 p2 = p1;
146                 p1 = lng;
147         }
148
149         if ( numb > 0 ) p2 ++;
150
151         return str.substring(p2, p1);
152         
153 }
154
155 unsigned char URIHasArg( String str, String arg ){
156
157
158         unsigned char a = 0, delimiterCnt = 0, lng = str.length();
159
160         for ( a = 0; a < lng; a++ ){
161                 if ( str.charAt( a ) == '/' ) delimiterCnt++;
162         }
163
164         for ( a=0; a < 10; a++ ){
165                 if ( arg.equals( expld( str, a, '/' ) ) ) return a;     
166         }
167         return 0;
168 }
169
170 // ---------------- EEPROM String r/w -------------------
171
172 void EEPROMStrRead( unsigned char addr, char * str ){
173         for (unsigned char a = addr; a <  EEPROM_STR_MAX_LEN; a++ ){
174                 str[ a ] = EEPROM.read( a );
175                 if (str[ a ] == 0) break;
176         }
177 }
178
179 void EEPROMStrWrite( unsigned char addr, char * str ){
180         unsigned char a = 0;
181         for (a = addr; a <  EEPROM_STR_MAX_LEN; a++ ){
182                 EEPROM.write( a, str[ a ] );
183                 if (str[ a ] == 0) break;
184         }
185         EEPROM.write( a, 0 );
186 }
187
188 // ---------------- misc ---------------------------------
189
190 int str2HEX(const String str) {    
191     return strtol( str.c_str(), 0, 16 );
192 }
193
194 void blink( unsigned char times ){
195         for (unsigned char a = 0; a < times; a++ ){
196                 digitalWrite(LED4, HIGH);
197                 delay (50);
198                 digitalWrite(LED4, LOW);
199                 delay (50);
200         }
201 }
202
203 // --------------- Init --------------------------------
204
205 void setup(void) {
206
207         HTTPresp.reserve(800); // lenght of Help message generally
208
209         // calculate lookup array 
210  
211         for (unsigned int a = 1; a < PWMSTEPS; a++) CIEL8[ a ] = round( pow( PWM_PERIOD, (double)a / (PWMSTEPS-1) ) ); // calculate exponential lookup array for PWM
212         CIEL8[ 0 ] = 0;
213
214         // init LED pins
215
216         pinMode(LED1, OUTPUT);
217         pinMode(LED2, OUTPUT);
218         pinMode(LED3, OUTPUT);
219         pinMode(LED4, OUTPUT);
220
221         digitalWrite(LED1, LOW);
222         digitalWrite(LED2, LOW);
223         digitalWrite(LED3, LOW);
224         digitalWrite(LED4, LOW);
225
226         // PWM inti
227
228         for (uint8_t channel = 0; channel < PWM_CHANNELS; channel++) pwm_duty_init[channel] = 0;        // Initial duty -> all off
229         uint32_t period = PWM_PERIOD;
230         pwm_init(period, pwm_duty_init, PWM_CHANNELS, io_info);
231         pwm_start();
232
233         // Serial init
234 #if SERIAL == 1
235         Serial.begin(115200);
236         Serial.println();
237         Serial.println("Booting Sketch...0");
238 #endif
239
240         // mDNS init
241
242 //      deviceURI.reserve(7);
243 //      deviceURI = "im" + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00
244
245         EEPROM.begin( EEPROM_STR_MAX_LEN + EEPROM_OTHER_MAX_LEN );
246         
247         if ( EEPROM.read( EEPROM_STR_FLAG ) == 1 ){
248                 EEPROMStrRead( EEPROM_STR, host ); 
249         } else {
250                 strcpy( host, HOST_DEFAULT ); // default URI and host name
251         }
252
253         WiFi.hostname( host );
254
255 //      WiFi.softAP(APssid, APpassword);
256
257 //      WiFi.mode(WIFI_AP);
258 //      WiFi.mode(WIFI_AP_STA); 
259         WiFi.mode(WIFI_STA);
260         WiFi.begin(ssid, password);
261
262         if (WiFi.waitForConnectResult() == WL_CONNECTED) {
263
264 //              MDNS.begin( deviceURI.c_str() );
265                 MDNS.begin( host );
266
267                 // default 
268
269                 server.onNotFound( []() {
270                         server.sendHeader("Connection", "close");
271
272                         unsigned int RGBW = 0xff;
273                         String param = "";
274                         String command = "";
275
276                         // parse plain URI arguments TODO: not sure whether we need it, check Plan 9 specifications
277 /*
278                         param = server.arg("rgbw");
279                         if ( param == "" ) {
280                                 param = URIHasArg( server.uri(), "rgbw" );
281                         }
282 */
283                         // color change 
284
285                         if ( server.hasArg("rgbw") ){ // || param != 0
286                                 
287                                 param = server.arg("rgbw");
288
289                                 Rnew = str2HEX( param.substring(0, 2) );
290                                 Gnew = str2HEX( param.substring(2, 4) );
291                                 Bnew = str2HEX( param.substring(4, 6) );
292                                 Wnew = str2HEX( param.substring(6, 8) );
293
294                                 changeRGBto( Rnew, Gnew, Bnew, Wnew );
295
296                                 R = Rnew;
297                                 G = Gnew;
298                                 B = Bnew;
299                                 W = Wnew;
300                         }
301
302                         // rotate
303
304                         if ( server.hasArg("rotate") ){
305                                 rotateDelay = str2HEX( server.arg("rotate") );
306                         }
307
308                         // rename host 
309
310                         if ( server.hasArg("rename") ) {
311                                 EEPROMStrWrite( EEPROM_STR, (char *)server.arg("rename").c_str() );
312                                 EEPROM.write( EEPROM_STR_FLAG, 1 );
313                                 EEPROM.commit();
314
315                                 server.sendHeader("Connection", "close");
316                                 server.send_P(200, "text/plain", PSTR("Done. System rebooting...\n") );
317
318                                 delay(500);
319                                 ESP.restart();
320                         }
321
322                         // ------
323
324                         HTTPresp = "host: " + String(host) + ".lan" + "\n";
325                         HTTPresp += "URI: " + server.uri() + "\n";
326 //                      HTTPresp += "Arg RGBW: " + String( URIHasArg( server.uri(), "test2" ) ) + "\n";         // URI contains /test2/
327 //                      HTTPresp += "Arg RGBW: " + server.arg("aaaa") + "\n";                                   // GET or POST params has "aaaa"
328                         HTTPresp += "rotate: " + String(rotateDelay) + "\n";
329                         HTTPresp += "rgbw: " + String(R) + " " + String(G) + " " + String(B) + " " + String(W) + "\n";
330                 
331                         server.send(200, "text/plain", HTTPresp);
332                 });
333
334                 // test EEPROM string
335
336                 server.on("/eeprom", HTTP_GET, []() {
337
338                         char * testStr2 = "asdfghjkl12345";
339
340                         server.setContentLength(CONTENT_LENGTH_UNKNOWN);
341                         server.send(200, "text/plain", "");
342
343                         EEPROMStrRead( EEPROM_STR, testStr2 );
344
345                         server.sendContent( testStr2 );
346                         server.sendContent( "\n" );
347                 });
348
349                 // help
350
351                 server.on("/help", HTTP_GET, []() {
352
353                         HTTPresp =      "Interplay Medium ESP8266 LED RGB(W) PWM Controller. Version: " + String(VERSION) + "\n";
354                         HTTPresp +=     "Written by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. \n\n";
355
356                         HTTPresp +=     " rgbw          Red Green Blue and White components in hexadecimal form (e.g. ff00ff00)\n";
357                         HTTPresp +=     " rotate        Delay of components rotation FX in hexadecimal (e.g. ffff), set 0 to stop\n";
358                         HTTPresp +=     " update        Wireless update of firmware (see example below)\n";
359                         HTTPresp +=     " help          Send this help\n\n";
360
361                         HTTPresp +=     "Usage:         curl http://" + String(host) + ".lan [--data \"rgbw=<hex RGBW>\"] [--data \"rotate=<hex delay, 0 = stop >\"] \n";
362                         HTTPresp +=     "               curl http://" + String(host) + ".lan[/rgbw/<hex RGBW>][/rotate/<hex delay>] \n";
363                         HTTPresp +=     "Examples:      curl http://" + String(host) + ".lan/?rgbw=00ff00ff&rotate=ff \n";
364                         HTTPresp +=     "               curl -F image=@RGBW_Controller.bin " + String(host) + ".lan/update \n";
365
366                         server.sendHeader("Connection", "close");
367                         server.send( 200, "text/plain", HTTPresp );
368
369                 });
370
371                 // OTA update
372
373                 server.on("/update", HTTP_POST, []() {
374
375                         server.sendHeader("Connection", "close");
376                         server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
377
378                         ESP.restart();
379
380                         }, []() {
381
382                                 HTTPUpload& upload = server.upload();
383
384                                 if (upload.status == UPLOAD_FILE_START) {
385
386                                         rotateDelay = 0;
387                                         blink( 3 );
388                                         
389 #if SERIAL == 1
390                                         Serial.setDebugOutput(true);
391 #endif
392                                         WiFiUDP::stopAll();
393
394 #if SERIAL == 1
395                                         Serial.printf("Update: %s\n", upload.filename.c_str());
396 #endif
397                                         uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
398
399                                         if (!Update.begin(maxSketchSpace)) { //start with max available size
400 #if SERIAL == 1
401                                                 Update.printError(Serial);
402 #endif
403                                         }
404                                 } else if (upload.status == UPLOAD_FILE_WRITE) {
405                                         if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
406 #if SERIAL == 1
407                                                 Update.printError(Serial);
408 #endif
409                                         }
410                                 } else if (upload.status == UPLOAD_FILE_END) {
411                                         if (Update.end(true)) { //true to set the size to the current progress
412
413                                                 server.sendHeader("Connection", "close");
414                                                 server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") );
415 #if SERIAL == 1
416                                                 Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize); 
417 #endif
418                                         } else {
419                                                 server.sendHeader("Connection", "close");
420                                                 server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") );
421 #if SERIAL == 1
422                                                 Update.printError(Serial);
423 #endif
424                                         }
425 #if SERIAL == 1
426                                         Serial.setDebugOutput(false);
427 #endif
428                                 }
429
430                                 yield();
431                 });
432
433                 // start server 
434
435                 server.begin();
436                 MDNS.addService("http", "tcp", 80);
437 #if SERIAL == 1
438                 Serial.printf("Ready! Open http://%s.local in your browser\n", host);
439 #endif
440
441         } else {
442 #if SERIAL == 1
443                 Serial.println("WiFi Failed");
444 #endif
445         }
446 }
447
448 void loop(void) {
449
450         server.handleClient();
451         MDNS.update();
452
453         eTimer ++;
454
455         if ( eTimer >= rotateDelay && rotateDelay != 0 ) {
456
457                 eTimer = 0;
458
459                 R = R + signR;
460                 G = G + signG;  
461                 B = B + signB;
462                 W = W + signW;
463
464                 if ( R >= PWMSTEPS-1 ) { signR = -1; R = PWMSTEPS-1; }
465                 if ( G >= PWMSTEPS-1 ) { signG = -1; G = PWMSTEPS-1; }
466                 if ( B >= PWMSTEPS-1 ) { signB = -1; B = PWMSTEPS-1; }
467                 if ( W >= PWMSTEPS-1 ) { signW = -1; W = PWMSTEPS-1; }
468
469                 if ( R < 2 ) { signR = 1; R = 2; }
470                 if ( G < 2 ) { signG = 1; G = 2; }
471                 if ( B < 2 ) { signB = 1; B = 2; }
472                 if ( W < 2 ) { signW = 1; W = 2; }
473
474                 pwm_set_duty( CIEL8[R], 0 );
475                 pwm_set_duty( CIEL8[G], 1 );
476                 pwm_set_duty( CIEL8[B], 2 );
477                 pwm_set_duty( CIEL8[W], 3 );
478
479                 pwm_start(); // commit
480
481         }
482 }
Contact me: dev (at) shalnoff (dot) com
PGP fingerprint: A6B8 3B23 6013 F18A 0C71 198B 83D8 C64D 917A 5717